#pragma once

#include <string>
#include <unistd.h>
#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>

#include "../Comm/Tool.hpp"
#include "../Comm/Log.hpp"

namespace Compiler_NS
{
    using namespace Tool_NS;
    using namespace Log_NS;

    class Compiler
    {
    public:
        Compiler() {};
        ~Compiler() {};

        // 这里的file_name只有文件名，并没有路径和后缀，所以我们需要将所需要的文件的路径和后缀拼接起来
        // file --> ./temp/file.cpp  源文件
        // file --> ./temp/file.exe  可执行程序
        // file --> ./temp/file.compile_error  用于保存编译时出错时出错信息的临时文件

        static bool Compile(const std::string &file_name)
        {
            // 修改默认权限掩码，使得我们创建文件时，是符合我们的预期的
            umask(0);
            // 编译错误，我们需要知道错误的原因
            // 打开用于保存编译时出错时出错信息的临时文件
            int compile_error_fd = open(PathTool::CompileError(file_name).c_str(), O_CREAT | O_WRONLY, 0644);
            if (compile_error_fd < 0)
            {
                LOG(Warning) << "创建存储报错编译错误信息的文件错误" << std::endl;
                exit(1);
            }

            // 重定向标准错误，使得子进程编译失败时，将错误信息写入到这个文件中
            // 后序的程序替换，并不会影响进程的文件描述符表
            dup2(compile_error_fd, 2);

            // 创建子进程，让子进程进行程序替换完成编译代码的任务
            pid_t pid = fork();

            if (pid < 0)
            {
                // 创建子进程失败
                LOG(Error) << "创建子进程失败" << std::endl;
                exit(1);
            }
            else if (pid == 0)
            {
                // 子进程
                // g++ src -o target -std=c++11
                // 注意execlp函数需要以nullptr结尾

                // 将后端代码基本写完后,编译源文件时，需要带上-DCOMPILE_ONLINE 选项
                // 因为测试代码中，为了语法提示和防止保存，我们包含了default_code.cpp
                // 而用户提交的代码和测试代码是我们手动合并的，所以编译的时候源文件并不知道default_code.cpp在哪
                // 所以我们这里要添加-DCOMPILE_ONLINE 选项将default_code.cpp的这段代码注释掉
                execlp("g++", "g++", PathTool::Src(file_name).c_str(), "-o", PathTool::Exe(file_name).c_str(), "-D", "COMPILE_ONLINE ", "-std=c++11", nullptr);

                LOG(Error) << "启动g++编译器失败" << std::endl;
                exit(1);
            }
            else
            {
                // 父进程
                waitpid(pid, nullptr, 0);

                // 若形成的可执行程序存在，则说明子进程编译代码成功，反之则失败
                if (FileTool::IsFileExists(PathTool::Exe(file_name)))
                {
                    LOG(Info) << PathTool::Exe(file_name) << "形成成功" << std::endl;
                    return true;
                }
                LOG(Error) << "未形成可执行程序" << std::endl;
                return false;
            }
        }
    };

}
